/*
 * =====================================================================================
 *
 *       Filename:  main.c
 *
 *    Description:
 *					1、主函数
 *
 *        Version:  1.0
 *        Created:  2019年2月21日 11时34分45秒
 *       Revision:  none
 *
 *         Author:  lixinde (lxd), lixinde@phytium.com.cn
 *        Company:  Phytium Technology Co.,Ltd
 *        License:  GPL-2.0+
 *
 * =====================================================================================
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <getopt.h>
#include "main_config.h"
#include "parameter.h"
#include "parameter_mcu.h"

static pm_list_fun_t const *pm_list[] = {
	&head_fun,
	&pll_fun,
	&mcu_fun,
	&peu_fun,
	&common_fun,
	&dev_fun,
	NULL,
};

static int run_ck_func_list(FILE *fp, uint64_t base)
{
	const pm_list_fun_t **fun_prt;
	int ret = 0;
	for(fun_prt = pm_list; *fun_prt; fun_prt++){
		if(!((*fun_prt)->ck_fnc))
			continue;
		ret = ((*fun_prt)->ck_fnc)(fp, base);
		if(ret){
			ERROR("\n");
			ERROR("Err: run check magicn --fun number: %ld\n",(fun_prt - pm_list) + 1);
			return ret;
		}
	}
	return 0;
}

/*检测模式及校验magic*/
/*成功 0 失败 -1*/
int check_para(FILE *const fp, parameter_base_t *p_base)
{
	uint32_t magic, ret;

	if(ret = run_ck_func_list(fp, p_base->addr))
		return ret;

	return 0;
}

static int run_prf_func_list(FILE *fp, parameter_base_t const *p_base)
{
	const pm_list_fun_t **fun_prt;
	int ret = 0;
	for(fun_prt = pm_list; *fun_prt; fun_prt++){
		if(!((*fun_prt)->prf_fnc))
			continue;
		ret = ((*fun_prt)->prf_fnc)(fp, p_base);
		if(ret){
			ERROR("\n");
			ERROR("Err: run print bin --fun number: %ld\n",(fun_prt - pm_list) + 1);
			return ret;
		}
	}
	return 0;
}

static int run_cfg_func_list(FILE *fp, pm_info_t *pm_d)
{
	const pm_list_fun_t **fun_prt;
	int ret = 0;
	for(fun_prt = pm_list; *fun_prt; fun_prt++){
		if(!((*fun_prt)->cfg_fnc))
			continue;
		ret = ((*fun_prt)->cfg_fnc)(fp, pm_d);
		if(ret){
			ERROR("\n");
			ERROR("Err: run config bin --fun number: %ld\n",(fun_prt - pm_list) + 1);
			return ret;
		}
	}
	return 0;
}

/**
 * @brief 输入多个文件，合并生成最终固件
 *
 * @param fip pbf 固件文件名
 * @param para 参数表文件名
 * @param boot BootLoader文件名（uboot uefi）
 * @param firm 最终生成固件文件名
 * @return int
 */
int generate_firmware_file(char *fip, char *para, char *boot, char *firm)
{
	FILE *fp_fip, *fp_para, *fp_boot, *fp_firm;
	int ret;
	long length;

	printf("%s\n",__func__);

	fp_fip = fopen(fip, "rb");
	if(!fp_fip)
	{
		perror("Could not open file");
		return 1;
	}

	fp_firm = fopen(firm, "wb");
	if(!fp_firm)
	{
		perror("Could not open file");
		return 1;
	}

	/* 复制pbf文件到生成文件 */
 	fseek(fp_fip, 0, SEEK_END);//回到文件的末尾,故返回的是文件的大小
	length = ftell(fp_fip);
	printf("pbf file size: 0x%lx\n", length);

	char *file_cp;
	file_cp = malloc(length);
	if(!file_cp)
	{
		printf("alloc failed!\n");
	}

	/* 回到文件头，读取数据 */
 	fseek(fp_fip, 0, SEEK_SET);\
 	fseek(fp_firm, 0, SEEK_SET);

	fread(file_cp, 1, length, fp_fip);
	fwrite(file_cp, 1, length, fp_firm);

	free(file_cp);

	/* 打开uboot二进制文件并计算文件大小 */
	fp_boot = fopen(boot, "rb");

	int boot_base, boot_size;
	boot_base = 0x180000;

	fseek(fp_boot, 0, SEEK_END);
	boot_size = ftell(fp_boot);

	/* 移动指针到相对于文件开头偏移 0xb0000 字节位置 */
	fseek(fp_firm, 0xb0000, SEEK_SET);

	fwrite(&boot_base, 1, 4, fp_firm);
	fwrite(&boot_size, 1, 4, fp_firm);

	printf("boot_base: %x\n", boot_base);
	printf("boot_size: %x\n", boot_size);

	char *file_boot;
	file_boot = malloc(boot_size);
	if(!file_boot)
	{
		printf("alloc failed!\n");
	}
	fseek(fp_boot, 0x00, SEEK_SET);
	fseek(fp_firm, boot_base, SEEK_SET);

	fread(file_boot, 1, length, fp_boot);
	fwrite(file_boot, 1, length, fp_firm);
	free(file_boot);

	fp_para = fopen(para, "rb");
	int para_size;
	char *file_pare;

	fseek(fp_para, 0, SEEK_END);
	para_size = ftell(fp_para);

	file_pare = malloc(para_size);
	if(!file_pare)
	{
		printf("alloc failed!\n");
	}

	fseek(fp_para, 0x00, SEEK_SET);
	fseek(fp_firm, 0x300000, SEEK_SET);

	fread(file_pare, 1, para_size, fp_para);
	fwrite(file_pare, 1, para_size, fp_firm);
	free(file_pare);

	fclose(fp_para);
	fclose(fp_fip);
	fclose(fp_firm);
	fclose(fp_boot);
}

int generate_parameter_file(char *filename, pm_info_t *pm_data)
{
	FILE * fp;
	int ret;
	printf("%s\n",__func__);
	fp = fopen(filename, "wb");

	if(fp == NULL)
	{
		printf("parameter_file open error\n");
		ret -1;
		goto error;
	}

	if(ret = run_cfg_func_list(fp, pm_data))
	{
		printf("parameter_file config error\n");
		ret -1;
		goto error;
	}

error:
	fclose(fp);
	return ret;
}

int dispaly_parameter_file(char *filename)
{
	FILE * fp;
	int ret;
	parameter_base_t p_base;
	p_base.addr = 0x00;

	printf("%s\n",__func__);

	fp = fopen(filename, "rb");

	if(fp == NULL)
	{
		ERROR("dispaly_parameter open error\n");
		return -1;
	}

	if(ret = check_para(fp, &p_base))
	{
		ERROR("Err : CAN NOT FIND MAGIC NUMBER , %d\n", ret);
		ret = -1;
		goto error;
	}

	if(ret = run_prf_func_list(fp, &p_base))
		goto error;
error:
	fclose(fp);
	return ret;
}

int init_pack_time(char * pack_time)
{
	int timeStrLen;

    time_t calendar_time = time(NULL);
    struct tm * tm_local = localtime(&calendar_time);
    timeStrLen = strftime(pack_time, 32, "%G-%m-%d %H:%M:%S", tm_local);
    printf("pack time :%s \n", pack_time);

	return timeStrLen;
}

static void usage()
{
	printf("do_config: D2000 gui config generate tool\n"
		"./do_config -i PBF.bin -f firmware.bin -g -d -p p.bin -b u-boot.bin\n"
		"\nUsage:\n"
		"  -d|	display parameter info\n"
		"  -g|	generate parameter bin file\n"
		"  -p|	parameter bin file\n"
		"  -f|	generate firmware bin file\n"
		"  -b|	bl33 bootloader bin file\n"
		);
}

int main(int argc, char *argv[])
{
	FILE *fp;
	int ret = 0;
	pm_info_t pm_data;
	int opt, idx;
	int display_info = 0;
	int generate_parameter = 0;

	char *parameter_filename = NULL;
	char *firmware_filename = NULL;
	char *boot_filename = NULL;
	char *pbf_firmware_filename = NULL;

	if(argc == 1)
	{
		usage();
		return 0;
	}

	init_pack_time(pm_data.pack_time);

	while ((opt = getopt(argc, argv, "dgp:f:b:i:")) != -1)
	{
		switch (opt) {
		case 'd':
			display_info = 1;
			break;
		case 'g':
			generate_parameter = 1;
			break;
		case 'p':
			parameter_filename = optarg;
			break;
		case 'f':
			firmware_filename = optarg;
			break;
		case 'b':
			boot_filename = optarg;
			break;
		case 'i':
			pbf_firmware_filename = optarg;
			break;
		}
	}

	printf("generate_parameter %d\n",generate_parameter);
	printf("display_info %d\n",display_info);
	printf("parameter_filename %s\n",parameter_filename);
	printf("firmware_filename %s\n",firmware_filename);
	printf("boot_filename %s\n",boot_filename);
	printf("pbf_firmware_filename %s\n",pbf_firmware_filename);

	if(generate_parameter && parameter_filename != NULL)
	{
		generate_parameter_file(parameter_filename, &pm_data);
	}

	if(display_info && parameter_filename != NULL)
	{
		dispaly_parameter_file(parameter_filename);
	}

	if(firmware_filename != NULL && pbf_firmware_filename != NULL
		&& boot_filename != NULL)
	{
		generate_firmware_file(pbf_firmware_filename, parameter_filename, boot_filename, firmware_filename);
	}

	return 0;
}